package hu.mapro.gwtui.gxt.client.form;

import hu.mapro.gwtui.client.Action;
import hu.mapro.gwtui.client.Handlers;
import hu.mapro.gwtui.client.MessageInterface;
import hu.mapro.gwtui.client.action.Refreshable;
import hu.mapro.gwtui.gxt.client.data.GxtDataUtils;
import hu.mapro.gwtui.gxt.client.form.CancelEvent.CancelHandler;
import hu.mapro.gwtui.gxt.client.form.SaveEvent.SaveHandler;

import java.util.List;

import com.google.common.base.Optional;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.editor.client.EditorError;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.AttachEvent.Handler;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.HasEnabled;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ResizeComposite;
import com.google.gwt.user.client.ui.Widget;
import com.google.web.bindery.requestfactory.shared.Receiver;
import com.google.web.bindery.requestfactory.shared.ServerFailure;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.data.shared.SortDir;
import com.sencha.gxt.data.shared.Store.StoreSortInfo;
import com.sencha.gxt.data.shared.loader.DataProxy;
import com.sencha.gxt.data.shared.loader.ListLoadConfig;
import com.sencha.gxt.data.shared.loader.ListLoadResult;
import com.sencha.gxt.data.shared.loader.ListLoader;
import com.sencha.gxt.data.shared.loader.LoadEvent;
import com.sencha.gxt.data.shared.loader.LoadHandler;
import com.sencha.gxt.data.shared.loader.LoadResultListStoreBinding;
import com.sencha.gxt.widget.core.client.ContentPanel;
import com.sencha.gxt.widget.core.client.button.ToolButton;
import com.sencha.gxt.widget.core.client.container.BorderLayoutContainer;
import com.sencha.gxt.widget.core.client.container.SimpleContainer;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.event.SelectEvent.SelectHandler;
import com.sencha.gxt.widget.core.client.event.ViewReadyEvent;
import com.sencha.gxt.widget.core.client.event.ViewReadyEvent.ViewReadyHandler;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridSelectionModel;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent.HasSelectionChangedHandlers;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent.SelectionChangedHandler;
import com.sencha.gxt.widget.core.client.toolbar.ToolBar;

public class EntitiesEditorPanel<T> extends ResizeComposite implements Refreshable {

	interface Binder extends UiBinder<Widget, EntitiesEditorPanel<? extends Object>> {
	}

	interface Style extends CssResource {
		String menuItemSelected();
	}
	
	private static final Binder binder = GWT.create(Binder.class);

	MessageInterface messageInterface;
	
	@UiField
	ContentPanel northContentPanel;


	
	
//	@UiField
//	TextButton newButton;
//	@UiField
//	TextButton deleteButton;
	
	@UiField(provided=true)
	Grid<T> grid;
	@UiField
	SimpleContainer centerContainer;
	@UiField
	Label noneSelectedLabel;
	@UiField
	ToolBar toolBar;
	
	@UiField
	BorderLayoutContainer rootContainer;
	
	
	FormFramePanel form;

	private ListStore<T> store;
	//List<Action> selectionChangeActions = Lists.newArrayList();

	public  EntitiesEditorPanel(
			ModelKeyProvider<? super T> keyProvider, 
			ColumnModel<T> cm 
	) {

		store = new ListStore<T>(keyProvider);

		grid = new Grid<T>(store, cm);
		
		grid.addAttachHandler(new Handler() {
			@Override
			public void onAttachOrDetach(AttachEvent event) {
				if (event.isAttached()) {
					grid.getView().refresh(true);
				}
			}
		});
		
		grid.addViewReadyHandler(new ViewReadyHandler() {
			@Override
			public void onViewReady(ViewReadyEvent event) {
				grid.getView().refresh(true);
			}
		});

		grid.getView().setForceFit(true);
		grid.setLoadMask(true);

		

		grid.getSelectionModel().addSelectionChangedHandler(
				new SelectionChangedHandler<T>() {
					@Override
					public void onSelectionChanged(
							SelectionChangedEvent<T> event) {
						fireSelectionChangedActions();
					}
				}
		);

		initWidget(binder.createAndBindUi(this));
		
		
		form = new FormFramePanel();
		
		form.addSaveHandler(new SaveHandler() {
			@Override
			public void onSave(SaveEvent event) {
				currentContext.get().save();
			}
		});
		form.addCancelHandler(new CancelHandler() {
			@Override
			public void onCancel(CancelEvent event) {
				currentContext.get().cancel();
			}
		});
		
		fireSelectionChangedActions();
	}

	public <C extends ListLoadConfig, D extends ListLoadResult<T>> void setDataProxy(DataProxy<C, D> proxy) {
		ListLoader<C, D> loader = new ListLoader<C, D>(proxy);
		loader.addLoadHandler(new LoadResultListStoreBinding<C, T, D>(
				store));
		loader.addLoadHandler(new LoadHandler<C, D>() {
			@Override
			public void onLoad(
					LoadEvent<C, D> event) {
				fireSelectionChangedActions();
			}
		});
		
		grid.setLoader(loader);
	}

//	public TextButton getNewButton() {
//		return newButton;
//	}
//
//	public TextButton getDeleteButton() {
//		return deleteButton;
//	}

	public Grid<T> getGrid() {
		return grid;
	}

	public SimpleContainer getCenterContainer() {
		return centerContainer;
	}

	public ListStore<T> getStore() {
		return store;
	}

	@SuppressWarnings("unchecked")
	public void addSortField(ValueProvider<T, ?> valueProvider, SortDir sortDir) {
		assert valueProvider!=null;
		store.addSortInfo(new StoreSortInfo<T>((ValueProvider)valueProvider, sortDir));
	}
	
	@Override
	public void refresh() {
		grid.getLoader().load();
	}

	private boolean suppressSelectionHandler = false;
	
	Handlers selectionHandlers = Handlers.newInstance();
	
	private void fireSelectionChangedActions() {
		if (suppressSelectionHandler) return;
		
		selectionHandlers.fire();
		
		final List<T> selection = grid.getSelectionModel().getSelectedItems();
		
		if (updateOperations!=null && selection.size()==1) {
			startEdit(new EditingContextUpdate(
					selection.get(0)
			));
		} else {
			hideForm();
		}
	}

	public void setGridHeader(String string) {
		northContentPanel.setHeadingText(string);
	}

	public void setNoneSelectedLabel(String string) {
		noneSelectedLabel.setText(string);
	}
	
	
	private void showForm() {
		setCurrentWidget(form);
	}
	
	private void hideForm() {
		setCurrentWidget(noneSelectedLabel);
	}
	
	private void setCurrentWidget(Widget widget) {
		centerContainer.setWidget(widget);
		redoLayout();
	}
	
	private void redoLayout() {
		Scheduler.get().scheduleDeferred(new ScheduledCommand() {
			@Override
			public void execute() {
				rootContainer.forceLayout();
			}
		});
	}


	public void addDeleteButton(final DeleteContext<T> ctx) {
		final ToolButton button = new ToolButton(ToolButton.MINUS);
		toolBar.add(button);
		
		GridSelectionModel<T> sm = grid.getSelectionModel();
		HasSelectionChangedHandlers<T> ch = sm;
		HasEnabled en = new HasEnabled() {
			
			@Override
			public void setEnabled(boolean enabled) {
				button.setEnabled(enabled);
			}
			
			@Override
			public boolean isEnabled() {
				return button.isEnabled();
			}
		};
		GxtDataUtils.whenHasSelection(ch, en);
		button.addSelectHandler(new SelectHandler() {
			@Override
			public void onSelect(SelectEvent event) {
				final List<T> selectedItems = grid.getSelectionModel().getSelectedItems();
				
				Action deleteAction = new Action() {

					@Override
					public void perform() {
						ctx.delete(selectedItems, new Receiver<Void>() {
							@Override
							public void onSuccess(Void response) {
								refresh();
							}

						});
					}
					
				};
				
				if (messageInterface==null) {
					deleteAction.perform();
				} else {
					messageInterface.confirm("Delete", "Are you sure you want to delete " + selectedItems.size() + " message(s)?" , deleteAction, null);
				}
				
				
			}
		});
	}
	
	public interface DeleteContext<T> {
		void delete(List<T> object, Receiver<Void> receiver);
	}

	public void setMessageInterface(MessageInterface messageInterface) {
		this.messageInterface = messageInterface;
	}




	public interface EditOperations<T> {

		boolean isDirty();

		void focus();

		List<EditorError> getErrors();

		boolean hasErrors();

		void flush();

		void cancel();

		String getHeading();

		Widget getWidget();

		void edit(T editingObject);

		
	}
	
	public interface CreateOperations<T> extends EditOperations<T> {

		T createNewObject();

		void persist(Receiver<Void> receiver);
		
	}
	
	public interface UpdateOperations<T> extends EditOperations<T> {
		
		void merge(Receiver<T> receiver);
		
	}
	
	public interface CreateUpdateOperations<T> extends CreateOperations<T>, UpdateOperations<T> {
		
	}
	
	CreateOperations<T> createOperations;
	UpdateOperations<T> updateOperations;
	Optional<EditingContext> currentContext = Optional.absent();

	
	private ToolButton createButton;
	
	public void setCreateOperations(CreateOperations<T> operations) {
		this.createOperations = operations;
		
		if (createButton==null) {
			createButton = new ToolButton(ToolButton.PLUS);
			toolBar.add(createButton);
			createButton.addSelectHandler(new SelectHandler() {
				@Override
				public void onSelect(SelectEvent event) {
					grid.getSelectionModel().deselectAll();
					
					startEdit(new EditingContextCreate());
				}
			});
		}
		
	}
	
	public void setUpdateOperations(UpdateOperations<T> operations) {
		this.updateOperations = operations;
		
	}
	
	public void setCreateUpdateOperations(CreateUpdateOperations<T> operations) {
		setCreateOperations(operations);
		setUpdateOperations(operations);
	}

	boolean confirmDiscard = false;
	
	private void startEdit(
			final EditingContext ctx
	) {
		if (currentContext.isPresent()) {
			currentContext.get().stopConfirmed(
					new Action() {
						@Override
						public void perform() {
							ctx.doStartEdit();
						}
					}
			);
		} else {
			ctx.doStartEdit();
		}
		
	}

	abstract class EditingContext {
		T editingObject;
		EditOperations<T> editOperations;

		private EditingContext(
				T editingObject, 
				EditOperations<T> editOperations
		) {
			super();
			this.editingObject = editingObject;
			this.editOperations = editOperations;
		}

		public void stopConfirmed(Action action) {
			if (editOperations.isDirty()) {
				messageInterface.confirm(
						"Discard Changes", 
						"You have unsaved changes. Are you sure you want to discard them?",
						action,
						new Action() {
							@Override
							public void perform() {
								suppressSelectionHandler = true;
								grid.getSelectionModel().select(editingObject, false);
								suppressSelectionHandler = false;
							}
						}
				);
			} else {
				action.perform();
			}
		}

		private void doStartEdit() {
			editOperations.edit(editingObject);
			form.setHeadingText(editOperations.getHeading());
			Widget widget = editOperations.getWidget();
			form.setMainContent(widget);

			showForm();
			
			currentContext = Optional.of(this);
			
			Scheduler.get().scheduleDeferred(new ScheduledCommand() {
				@Override
				public void execute() {
					editOperations.focus();
				}
			});
		}

		public void save() {
			editOperations.flush();
			
			if (editOperations.hasErrors()) {
				StringBuffer sb = new StringBuffer();
				for (EditorError error : editOperations.getErrors()) {
					sb.append(error.getMessage());
					sb.append("\n");
				}
				
				messageInterface.alert("Validation Failed", sb.toString(), null);
				
				return;
			}
			
			form.getSaveButton().setEnabled(false);
			save(new Receiver<T>() {
				@Override
				public void onSuccess(T response) {
					currentContext = Optional.absent();
					updateListAfterEdit(response);
					form.getSaveButton().setEnabled(true);
				}
				
				@Override
				public void onFailure(ServerFailure error) {
					if (messageInterface==null) {
						super.onFailure(error);
					} else {
						messageInterface.alert("Save Error", error.getMessage(), null);
					}
					form.getSaveButton().setEnabled(true);
				}
			});
			
			
		}

		private void cancel() {
			editOperations.cancel();
			
			afterCancel();
		}
		
		abstract void afterCancel();

		abstract void save(Receiver<T> receiver);

		void updateListAfterEdit(T response) {
			store.add(response);
			grid.getSelectionModel().select(response, false);
			grid.getView().focusRow(store.indexOf(response));
		}
		
	}
	
	class EditingContextCreate extends EditingContext {

		public EditingContextCreate() {
			super(createOperations.createNewObject(), createOperations);
		}
		
		@Override
		void save(final Receiver<T> receiver) {
			createOperations.persist(new Receiver<Void>() {

				@Override
				public void onSuccess(Void response) {
					receiver.onSuccess(editingObject);
				}
			});
		}

		@Override
		void afterCancel() {
			hideForm();
		}
		
	}
	
	class EditingContextUpdate extends EditingContext {

		public EditingContextUpdate(T editingObject) {
			super(editingObject, updateOperations);
		}
		
		@Override
		void save(Receiver<T> receiver) {
			updateOperations.merge(receiver);
		}
		
		void updateListAfterEdit(T response) {
			store.remove(editingObject);
			super.updateListAfterEdit(response);
		};

		@Override
		void afterCancel() {
			grid.getSelectionModel().deselectAll();
		}
	}

	public ToolBar getToolBar() {
		return toolBar;
	}

	public Handlers getSelectionHandlers() {
		return selectionHandlers;
	}
	
}
